/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.syntax.builder;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

import org.modelcc.io.java.Reflection;
import org.modelcc.language.factory.SymbolIdentifier;
import org.modelcc.language.factory.ListContent;
import org.modelcc.language.metamodel.MemberCollection;
import org.modelcc.language.metamodel.CompositeLanguageElement;
import org.modelcc.language.metamodel.LanguageModel;
import org.modelcc.language.metamodel.LanguageMember;
import org.modelcc.language.syntax.ParserMetadata;
import org.modelcc.language.syntax.RuleSymbol;
import org.modelcc.language.syntax.Symbol;
import org.modelcc.language.syntax.SymbolBuilder;

/**
 * Reference symbol builder.
 * 
 * @author Luis Quesada (lquesada@modelcc.org) & Fernando Berzal (fberzal@modelcc.org)
 */
public final class ReferenceSymbolBuilder extends SymbolBuilder implements Serializable 
{
    /**
     * Constructor
     */
    public ReferenceSymbolBuilder(LanguageModel model) 
    {
    	super(model);
    }
    
    /**
     * Build a symbol.
     * @param t the symbol to be built.
     * @param data the parser metadata.
     * @return true if the symbol is valid, false if not
     */
    @Override
	public boolean build(Symbol t, ParserMetadata data) 
    {
        SymbolIdentifier eid = (SymbolIdentifier)t.getType();
        CompositeLanguageElement ce = (CompositeLanguageElement) eid.getElement();
        Class c = ce.getElementClass();
        Object o;
        boolean valid = true;

        try {
            o = c.newInstance();
            Set<RuleSymbol> proc = new HashSet<RuleSymbol>();
            Set<Field> filled = new HashSet<Field>();
            for (int i = 0;i < t.size();i++) {
            	Symbol s = t.getContent(i);
            	RuleSymbol re = t.getElement(i);
            	proc.add(re);
            	if (re.getLanguageMember()!=null) {
            		LanguageMember ct = re.getLanguageMember();
            		Field fld = Reflection.findField(c,ct.getID());
            		filled.add(fld);
            		Object list;
            		Method addm;
            		int j;
            		Object[] listData;
            		if (fld != null) {
            			fld.setAccessible(true);
            			if (!ct.getClass().equals(MemberCollection.class)) {
            				fld.set(o,s.getUserData());
            			} else {
            				MemberCollection mc = (MemberCollection)ct;
            				ListContent listContents = (ListContent) s.getUserData();
            				listData = listContents.getContent();
            				Symbol extra = listContents.getSymbol();
            				RuleSymbol extraRe = listContents.getRuleSymbol();
            				if (extraRe != null) {
            					LanguageMember extraCt = extraRe.getLanguageMember();
            					Field extraFld = Reflection.findField(c,extraCt.getID());
            					filled.add(extraFld);
            					if (extraFld != null) {
            						extraFld.setAccessible(true);
            						extraFld.set(o,extra.getUserData());
            					}
            				}
            				if (listData.length<mc.getMinimumMultiplicity())
            					valid = false;
            				if (mc.getMaximumMultiplicity() != -1) {
            					if (listData.length>mc.getMaximumMultiplicity())
            						valid = false;
            				}
            				switch (mc.getCollection()) {
            				case LIST:
            					if (fld.getType().isInterface())
            						list = ArrayList.class.newInstance();
            					else
            						list = fld.getType().newInstance();
            					addm = list.getClass().getDeclaredMethod("add",Object.class);
            					if (listData != null)
            						for (j = 0;j < listData.length;j++)
            							addm.invoke(list,listData[j]);
            					fld.set(o,list);
            					break;
            				case ARRAY:
            					list = Array.newInstance(ct.getElementClass(),listData.length);
            					if (listData != null)
            						for (j = 0;j < listData.length;j++)
            							Array.set(list, j, listData[j]);
            					fld.set(o,list);
            					break;
            				case SET:
            					if (fld.getType().isInterface())
            						list = HashSet.class.newInstance();
            					else
            						list = fld.getType().newInstance();
            					addm = list.getClass().getDeclaredMethod("add",Object.class);
            					if (listData != null)
            						for (j = 0;j < listData.length;j++)
            							addm.invoke(list,listData[j]);
            					fld.set(o,list);
            					break;
            				}
            			}
            		}
            	}
            }

        } catch (Exception ex) {
            log(Level.SEVERE, null, ex);
            return false;
        }

        if (ce.getKeyMembers().isEmpty())
            valid = false;
        
        if (valid)
        	data.addReference(o, t);
        
        return valid;
    }

}
